//go:build linux && amd64

// nolint: unused

package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"sync/atomic"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/google/gopacket/tcpassembly"
)

var device = flag.String("i", "eth0", "Interface to get packets from")
var packetCount = -1
var nBytes atomic.Uint64
var nBytesV4 atomic.Uint64
var nBytesV6 atomic.Uint64
var totalBytes atomic.Uint64
var totalSkipBytes atomic.Uint64

type statsStreamFactory struct{}

type statsStream struct {
	net, transport                      gopacket.Flow
	bytes, packets, outOfOrder, skipped int64
	start, end                          time.Time
	sawStart, sawEnd                    bool
}

func (factory *statsStreamFactory) New(net, transport gopacket.Flow) tcpassembly.Stream {
	// log.Printf("new stream %v:%v started", net, transport)
	s := &statsStream{
		net:       net,
		transport: transport,
		start:     time.Now(),
	}
	s.end = s.start
	return s
}

// Reassembled is called whenever new packet data is available for reading.
// Reassembly objects contain stream data IN ORDER.
func (s *statsStream) Reassembled(reassemblies []tcpassembly.Reassembly) {
	for _, reassembly := range reassemblies {
		if reassembly.Seen.Before(s.end) {
			s.outOfOrder++
		} else {
			s.end = reassembly.Seen
		}
		byteCount := int64(len(reassembly.Bytes))
		totalBytes.Add(uint64(byteCount))
		s.bytes += byteCount
		s.packets += 1
		if reassembly.Skip > 0 {
			skipByteCount := int64(reassembly.Skip)
			totalSkipBytes.Add(uint64(skipByteCount))
			s.skipped += skipByteCount
		}
		s.sawStart = s.sawStart || reassembly.Start
		s.sawEnd = s.sawEnd || reassembly.End
	}
}

// ReassemblyComplete is called when the TCP assembler believes a stream has
// finished.
func (s *statsStream) ReassemblyComplete() {
	//if s.skipped > 0 {
	//	log.Printf("stream %v terminated with skip=%d (可能原因: %v)",
	//		s.transport, s.skipped,
	//		map[bool]string{
	//			s.sawEnd:  "正常结束",
	//			!s.sawEnd: "超时强制关闭",
	//		}[s.sawEnd])
	//}
	// diffSecs := float64(s.end.Sub(s.start)) / float64(time.Second)
	// log.Printf("Reassembly of stream %v:%v complete - start:%v end:%v bytes:%v packets:%v ooo:%v bps:%v pps:%v skipped:%v",
	//	s.net, s.transport, s.start, s.end, s.bytes, s.packets, s.outOfOrder, int64(float64(s.bytes)/diffSecs), int64(float64(s.packets)/diffSecs), s.skipped)
}

func pcapMain() {
	flushDuration, err := time.ParseDuration("1m")
	if err != nil {
		return
	}
	handle, err := pcap.OpenLive(*device, 65536, true, pcap.BlockForever)
	if err != nil {
		log.Fatal("error opening pcap handle: ", err)
	}
	ports := []string{"15550", "15551", "15552", "15553", "15554", "15555", "15556", "15557", "15558", "15559", "16550", "16551", "16552", "16553", "16554", "16555", "16556", "16557", "16558", "16559", "7171", "8181"}
	var bpfParts []string
	for _, port := range ports {
		bpfParts = append(bpfParts, fmt.Sprintf("dst port %s", port))
	}
	bpfFilter := strings.Join(bpfParts, " or ")
	if err := handle.SetBPFFilter(bpfFilter); err != nil {
		log.Fatal("error setting BPF filter: ", err)
	}

	streamFactory := &statsStreamFactory{}
	streamPool := tcpassembly.NewStreamPool(streamFactory)
	assembler := tcpassembly.NewAssembler(streamPool)
	assembler.MaxBufferedPagesPerConnection = 400
	assembler.MaxBufferedPagesTotal = 9600
	log.Println("reading in packets")
	var eth layers.Ethernet
	var dot1q layers.Dot1Q
	var ip4 layers.IPv4
	var ip6 layers.IPv6
	var ip6extensions layers.IPv6ExtensionSkipper
	var tcp layers.TCP
	var payload gopacket.Payload
	parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &dot1q, &ip4, &ip6, &ip6extensions, &tcp, &payload)
	decoded := make([]gopacket.LayerType, 0, 4)

	nextFlush := time.Now().Add(flushDuration / 2)

	var byteCount int64
	go func() {
		ticker := time.NewTicker(10 * time.Second)
		defer ticker.Stop()
		for range ticker.C {
			log.Printf("total no bytes: %d, v4 no bytes: %d, v6 no bytes: %d, total bytes: %d, total skip bytes: %d",
				nBytes.And(0)*8/10, nBytesV4.And(0)*8/10, nBytesV6.And(0)*8/10, totalBytes.And(0)*8/10, totalSkipBytes.And(0)*8/10)
		}
	}()
	start := time.Now()

loop:
	for ; packetCount != 0; packetCount-- {
		if time.Now().After(nextFlush) {
			stats, _ := handle.Stats()
			log.Printf("pcap stats: %+v", stats)
			assembler.FlushOlderThan(time.Now().Add(flushDuration))
			nextFlush = time.Now().Add(flushDuration / 2)
		}
		data, ci, err := handle.ZeroCopyReadPacketData()
		if err != nil {
			log.Printf("error getting packet: %v", err)
			continue
		}
		err = parser.DecodeLayers(data, &decoded)
		if err != nil {
			log.Printf("error decoding packet: %v", err)
			continue
		}
		//	log.Printf("decoded the following layers: %v", decoded)
		byteCount = int64(len(data))
		nBytes.Add(uint64(byteCount))
		foundNetLayer := false
		var netFlow gopacket.Flow
		for _, typ := range decoded {
			switch typ {
			case layers.LayerTypeIPv4:
				netFlow = ip4.NetworkFlow()
				foundNetLayer = true
				nBytesV4.Add(uint64(byteCount))
			case layers.LayerTypeIPv6:
				netFlow = ip6.NetworkFlow()
				foundNetLayer = true
				nBytesV6.Add(uint64(byteCount))
			case layers.LayerTypeTCP:
				if foundNetLayer {
					assembler.AssembleWithTimestamp(netFlow, &tcp, ci.Timestamp)
				} else {
					log.Println("could not find IPv4 or IPv6 layer")
				}
				continue loop
			}
		}
		log.Println("could not find TCP layer")
	}
	assembler.FlushAll()
	log.Printf("processed %d bytes in %v", byteCount, time.Since(start))
}
